#include "AsioImpl.h"

#include <iostream>
#include <cassert>
#include <boost/bind/bind.hpp>

#include "Interface.h"
#include "Error.h"


using boost::asio::ip::make_address_v4;

namespace frame {
namespace impl {
namespace asio {


AsioChannelImpl::AsioChannelImpl(ChannelHandlerImpl::Ptr handler, 
    boost::asio::io_context & io, const std::string & ip, int remotePort)
: opened(false)
, handler(handler)
, io(io)
, socket(io)
, endpoint(make_address_v4(ip), remotePort)
{
    std::cout << "AsioChannelImpl:constructor: " << endpoint << std::endl;

}
AsioChannelImpl::~AsioChannelImpl(){
    std::cout << "AsioChannelImpl:destructor" << std::endl;
}
int AsioChannelImpl::open() {
    std::cout << "AsioChannelImpl:open:" << opened << std::endl;
    if(!opened){
        opened = true;
    }
    return 0;
}

int AsioChannelImpl::read(IReadBufferImpl::Ptr readBuffer){
    std::cout << "AsioChannelImpl:read" << std::endl;
    if(isOpen()){
        this->readBuffer = readBuffer;
        socket.async_read_some(boost::asio::mutable_buffer(readBuffer->buffer(), readBuffer->freeBytes()),
            boost::bind(&AsioChannelImpl::handleRead, shared_from_this(),
                boost::asio::placeholders::error, 
                boost::asio::placeholders::bytes_transferred
            )
        ); 
        return 0;
    }
    return -1;
}

void AsioChannelImpl::handleRead(const boost::system::error_code & error
    , size_t bytes_transferred){
    std::cout << "AsioChannelImpl::handleRead: " << error << std::endl;
    auto hdlr = getHandlerImpl();
    if(!error){
        if(hdlr != nullptr){
            readBuffer->fakePut(bytes_transferred); // limit-> increased
            std::cout << "read bytes: " << bytes_transferred << std::endl;
            //after read data =>  [data, data+bytes_transferred);
            hdlr->read(shared_from_this(), (const char*)readBuffer->buffer() - bytes_transferred, bytes_transferred);
        }
    }
    else{
        switch(error.value()){
            case boost::asio::error::operation_aborted :{
                std::cout << "AsioChannelImpl::read operation aborted: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->readException(shared_from_this(), 
                        Error::OperationAbort);
                }
                break;
            }
            case boost::asio::error::eof : {
                std::cout << "AsioChannelImpl::read end of file: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->disconnected(shared_from_this(), 
                        Error::PassiveClose);
                }   
                this->close();
                break;
            }
            case boost::asio::error::bad_descriptor : {
                std::cout << "AsioChannelImpl::read error, bad descriptor: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->disconnected(shared_from_this(), 
                        Error::ActiveClose);
                }   
                this->close();
                break;
            }   
            default : {
                std::cout << "AsioChannelImpl::handle read got error:[" << error << "]" << std::endl;
                if(hdlr != nullptr){
                    //TODO: map asio error (?) to ? 
                    hdlr->readException(shared_from_this(), error.value()); 
                }
                //Important: unknown read exception,
                // and then fire the ActiveClose to release resource
                this->disconnect();
                break;
            }
        }// end of switch
    }//end of else 
}

int AsioChannelImpl::write(IWriteBufferImpl::Ptr writeBuffer){
    std::cout << "AsioChannelImpl:write" << std::endl;
    if(isOpen()){
        this->writeBuffer = writeBuffer;
        socket.async_write_some(boost::asio::const_buffer(writeBuffer->data(), writeBuffer->inBytes()), 
            boost::bind(&AsioChannelImpl::handleWrited, shared_from_this(), 
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred
            )
        );
        return 0;
    }
    return -1;
}

void AsioChannelImpl::handleWrited(const boost::system::error_code & error
        , size_t bytes_transferred){
    std::cout << "AsioChannelImpl::handleWrited: " << error << std::endl;
    auto hdlr = getHandlerImpl();
    if(!error){
        if(hdlr != nullptr){
            this->writeBuffer->fakeGet(bytes_transferred);
            std::cout << "write bytes: " << bytes_transferred << std::endl;
            hdlr->writed(shared_from_this(), bytes_transferred);
        }
    }
    else{
        switch(error.value()){
            case boost::asio::error::operation_aborted :{
                std::cout << "AsioChannelImpl::write operation aborted: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->writeException(shared_from_this(), 
                        Error::OperationAbort);
                }
                break;
            }
            case boost::asio::error::eof : {
                std::cout << "AsioChannelImpl::write end of file: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->disconnected(shared_from_this(), 
                        Error::PassiveClose);
                }   
                this->close();
                break;
            }
            case boost::asio::error::bad_descriptor : {
                std::cout << "AsioChannelImpl::write Error, bad descriptor: "<< error << std::endl;
                if(hdlr != nullptr){
                    hdlr->disconnected(shared_from_this(), 
                        Error::ActiveClose);
                }   
                this->close();
                break;
            }   
            default : {
                std::cout << "AsioChannelImpl::handle write got error:[" << error << "]" << std::endl;
                if(hdlr != nullptr){
                    //TODO: map asio error (?) to ? 
                    hdlr->writeException(shared_from_this(), error.value()); 
                }
                this->disconnect();//fire the ActiveClose
                break;
            }
        }//end of switch 
    }
}

int AsioChannelImpl::close() {
    std::cout << "AsioChannelImpl:close" << std::endl;
    if(opened){
        this->disconnect();
        opened = false;
    }
    return 0;
}

int AsioChannelImpl::disconnect() {
    if(isOpen()){
        std::cout << "AsioChannelImpl:disconnect" << std::endl;
        //TODO: This function is used to close the socket. 
        // Any asynchronous send, receive or connect operations will be cancelled immediately
        // , and will complete with the boost::asio::error::operation_aborted error.
        if(socket.is_open())
            socket.close();
    }
    return 0;
}


AsioServiceImpl::AsioServiceImpl() 
{
}
AsioServiceImpl::~AsioServiceImpl() {
    std::cout <<"AsioServiceImpl::destructor"<< std::endl;
}
//TODO:call from Service 
int AsioServiceImpl::start() {
    std::cout <<"AsioServiceImpl::start"<< std::endl;
    return 0;
}
int AsioServiceImpl::run() {
    std::cout <<"AsioServiceImpl::run"<< std::endl;
    io.run();
    return 0;
}
void AsioServiceImpl::stop(){
    std::cout <<"AsioServiceImpl::stop"<< std::endl;
}






AsioClientImpl::AsioClientImpl(AsioServiceImpl::Ptr serviceImpl, ConnectHandlerImpl::Ptr connectHandlerImpl)
    : serviceImpl(serviceImpl)
    , connectHandlerImpl(connectHandlerImpl)
      , io(serviceImpl->getIoContext())
{
    std::cout << "AsioClientImpl:constructor" << std::endl;
}

AsioClientImpl::~AsioClientImpl(){
    std::cout << "AsioClientImpl:destructor" << std::endl;
}

void AsioClientImpl::asyncConnect(std::shared_ptr<AsioChannelImpl> impl){
    assert(impl != nullptr);
    impl->getSocket().async_connect(impl->getEndpoint(), 
            boost::bind(&AsioClientImpl::handleConnect, shared_from_this(),
                impl,
                boost::asio::placeholders::error
                )
            );
}
void AsioClientImpl::handleConnect(std::shared_ptr<AsioChannelImpl> impl, 
    const boost::system::error_code & error){
    std::cout << "AsioChannelImpl::handleConnect: " << error << std::endl;
    if(!error){
        impl->open();
        if(connectHandlerImpl != nullptr)
            connectHandlerImpl->connected(impl);
    }
    else{
        switch(error.value()){
            case boost::asio::error::operation_aborted :{
                if(connectHandlerImpl != nullptr){
                    connectHandlerImpl->connectFailed(impl, Error::OperationAbort);
                }
                std::cout << "connect operation aborted: "<< error << std::endl;
                break;
            }
            default : {
                std::cout << "handle connect got error:[" << error << "]" << std::endl;
                if(connectHandlerImpl != nullptr){
                    //TODO: map asio error (?) to ? 
                    connectHandlerImpl->connectFailed(impl, error.value());
                }
                break;
            }
        }// end of switch
    }
}

ChannelImpl::Ptr AsioClientImpl::connect(const std::string & remoteIp, const int remotePort, ChannelHandlerImpl::Ptr channelHandlerImpl){
    std::cout << "AsioClientImpl:connect" << std::endl;

    auto channelImpl = std::make_shared<AsioChannelImpl>(channelHandlerImpl, io, remoteIp, remotePort);
    asyncConnect(channelImpl);
    return channelImpl;
}
int AsioClientImpl::open() { // init the client 's resource 
    std::cout << "AsioClientImpl::open .." << std::endl;
    return 0;
}
int AsioClientImpl::close() { //close the client 's resource
    std::cout << "AsioClientImpl::close .." << std::endl;
    //channel resource(socket,mem) is released by the asio framework
    //AsioChannelImpl released by smartPtr when client is destructed
    return 0;
}





ClientImpl::Ptr AsioImplFactory::createClient(ServiceImpl::Ptr serviceImpl, ConnectHandlerImpl::Ptr connectHandlerImpl) {
    auto impl = std::static_pointer_cast<AsioServiceImpl>(serviceImpl);
    assert(impl != nullptr);
    return std::make_shared<AsioClientImpl>(impl, connectHandlerImpl);
}
ServiceImpl::Ptr AsioImplFactory::createService() {
    return std::make_shared<AsioServiceImpl>();
}

} // end of namespace asio
} // end of namespace impl
} // end of namespace frame
